// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/wv_test_license_server_config.h"

#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/log/net_log_source.h"
#include "net/socket/tcp_server_socket.h"
#include "net/test/python_utils.h"

const uint16_t kMinPort = 17000;
const uint16_t kPortRangeSize = 1000;

// Widevine license server configuration files.
const base::FilePath::CharType kKeysFileName[] =
    FILE_PATH_LITERAL("keys.dat");
const base::FilePath::CharType kPoliciesFileName[] =
    FILE_PATH_LITERAL("policies.dat");
const base::FilePath::CharType kProfilesFileName[] =
    FILE_PATH_LITERAL("profiles.dat");

// License server configuration files directory name relative to root.
const base::FilePath::CharType kLicenseServerConfigDirName[] =
    FILE_PATH_LITERAL("config");

WVTestLicenseServerConfig::WVTestLicenseServerConfig() {
}

WVTestLicenseServerConfig::~WVTestLicenseServerConfig() {
}

bool WVTestLicenseServerConfig::GetServerCommandLine(
    base::CommandLine* command_line) {
  if (!GetPythonCommand(command_line)) {
    LOG(ERROR) << "Could not get Python runtime command.";
    return false;
  }

  base::FilePath license_server_path;
  GetLicenseServerPath(&license_server_path);
  if (!base::PathExists(license_server_path)) {
    LOG(WARNING) << "Missing license server file at "
                 << license_server_path.value();
    return false;
  }

  base::FilePath server_root;
  GetLicenseServerRootPath(&server_root);
  base::FilePath config_path = server_root.Append(kLicenseServerConfigDirName);

  if (!base::PathExists(config_path.Append(kKeysFileName)) ||
      !base::PathExists(config_path.Append(kPoliciesFileName)) ||
      !base::PathExists(config_path.Append(kProfilesFileName))) {
    LOG(WARNING) << "Missing license server configuration files at "
                 << config_path;
    return false;
  }

  if (!SelectServerPort())
    return false;

  // Needed to dynamically load .so libraries used by license server.
  // TODO(shadi): Remove need to set env variable once b/12932983 is fixed.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  std::unique_ptr<base::Environment> env(base::Environment::Create());
  const char kLibraryPathEnvVarName[] = "LD_LIBRARY_PATH";
  std::string library_paths(license_server_path.DirName().value());
  std::string old_path;
  if (env->GetVar(kLibraryPathEnvVarName, &old_path))
    library_paths.append(":").append(old_path);
  env->SetVar(kLibraryPathEnvVarName, library_paths);
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)

  // Since it is a Python command line, we need to AppendArg instead of
  // AppendSwitch so that the arguments are passed to the Python server instead
  // of Python engine.
  command_line->AppendArgPath(license_server_path);
  command_line->AppendArg("-k");
  command_line->AppendArgPath(config_path.Append(kKeysFileName));
  command_line->AppendArg("-o");
  command_line->AppendArgPath(config_path.Append(kPoliciesFileName));
  command_line->AppendArg("-r");
  command_line->AppendArgPath(config_path.Append(kProfilesFileName));
  command_line->AppendArg(base::StringPrintf("--port=%u", port_));
  return true;
}

absl::optional<base::EnvironmentMap>
WVTestLicenseServerConfig::GetServerEnvironment() {
  // Add the Python protocol buffers files directory to Python path.
  base::FilePath pyproto_dir;
  if (!GetPyProtoPath(&pyproto_dir)) {
    LOG(WARNING) << "Cannot find pyproto directory required by license server.";
    return absl::nullopt;
  }

  base::EnvironmentMap map;
  SetPythonPathInEnvironment({pyproto_dir}, &map);
  return map;
}

bool WVTestLicenseServerConfig::SelectServerPort() {
  // Try all ports within the range of kMinPort to (kMinPort + kPortRangeSize)
  // Instead of starting from kMinPort, use a random port within that range.
  uint16_t start_seed = base::RandInt(0, kPortRangeSize);
  uint16_t try_port = 0;
  for (uint16_t i = 0; i < kPortRangeSize; ++i) {
    try_port = kMinPort + (start_seed + i) % kPortRangeSize;
    net::NetLogSource source;
    net::TCPServerSocket sock(NULL, source);
    if (sock.Listen(net::IPEndPoint(net::IPAddress::IPv4Localhost(), try_port),
                    1) == net::OK) {
      port_ = try_port;
      return true;
    }
  }
  LOG(WARNING) << "Could not find an open port in the range of " << kMinPort
               << " to " << kMinPort + kPortRangeSize;
  return false;
}

bool WVTestLicenseServerConfig::IsPlatformSupported() {
// TODO(crbug.com/1175344): Reenable OS_LINUX once license server
// (or Widevine CDM) updated.
#if BUILDFLAG(IS_CHROMEOS) && defined(ARCH_CPU_X86_64)
  return true;
#else
  return false;
#endif  // BUILDFLAG(IS_CHROMEOS) && defined(ARCH_CPU_X86_64)
}

std::string WVTestLicenseServerConfig::GetServerURL() {
  return base::StringPrintf("http://localhost:%u/license_server", port_);
}

void WVTestLicenseServerConfig::GetLicenseServerPath(base::FilePath *path) {
  base::FilePath server_root;
  GetLicenseServerRootPath(&server_root);
  // Platform-specific license server binary path relative to root.
  *path =
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
      server_root.Append(FILE_PATH_LITERAL("linux"))
          .Append(FILE_PATH_LITERAL("license_server.py"));
#else
    server_root.Append(FILE_PATH_LITERAL("unsupported_platform"));
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
}

void WVTestLicenseServerConfig::GetLicenseServerRootPath(
    base::FilePath* path) {
  base::FilePath source_root;
  base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root);
  *path = source_root.Append(FILE_PATH_LITERAL("third_party"))
                     .Append(FILE_PATH_LITERAL("widevine"))
                     .Append(FILE_PATH_LITERAL("test"))
                     .Append(FILE_PATH_LITERAL("license_server"));
}
